<html>

<head>
  <title>Accessible mutation events coalescence testing</title>

  <link rel="stylesheet" type="text/css"
        href="chrome://mochikit/content/tests/SimpleTest/test.css" />

  <script type="application/javascript"
          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
  <script type="application/javascript"
          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>

  <script type="application/javascript"
          src="../common.js"></script>
  <script type="application/javascript"
          src="../states.js"></script>
  <script type="application/javascript"
          src="../events.js"></script>

  <script type="application/javascript">

    ////////////////////////////////////////////////////////////////////////////
    // Invoker base classes

    const kRemoveElm = 1;
    const kHideElm = 2;
    const kAddElm = 3;
    const kShowElm = 4;

    /**
     * Base class to test of mutation events coalescence.
     */
    function coalescenceBase(aChildAction, aParentAction,
                             aPerformActionOnChildInTheFirstPlace)
    {
      // Invoker interface

      this.invoke = function coalescenceBase_invoke()
      {
        if (aPerformActionOnChildInTheFirstPlace) {
          this.invokeAction(this.childNode, aChildAction);
          this.invokeAction(this.parentNode, aParentAction);
        } else {
          this.invokeAction(this.parentNode, aParentAction);
          this.invokeAction(this.childNode, aChildAction);
        }
      }

      this.getID = function coalescenceBase_getID()
      {
        var childAction = this.getActionName(aChildAction) + " child";
        var parentAction = this.getActionName(aParentAction) + " parent";

        if (aPerformActionOnChildInTheFirstPlace)
          return childAction + " and then " + parentAction;

        return parentAction + " and then " + childAction;
      }

      this.finalCheck = function coalescenceBase_check()
      {
        if (this.getEventType(aChildAction) == EVENT_HIDE) {
          testIsDefunct(this.child);
        }
        if (this.getEventType(aParentAction) == EVENT_HIDE) {
          testIsDefunct(this.parent);
        }
      }

      // Implementation details

      this.invokeAction = function coalescenceBase_invokeAction(aNode, aAction)
      {
        switch (aAction) {
          case kRemoveElm:
            aNode.parentNode.removeChild(aNode);
            break;

          case kHideElm:
            aNode.style.display = "none";
            break;

          case kAddElm:
            if (aNode == this.parentNode)
              this.hostNode.appendChild(this.parentNode);
            else
              this.parentNode.appendChild(this.childNode);
            break;

          case kShowElm:
            aNode.style.display = "block";
            break;

          default:
            return INVOKER_ACTION_FAILED;
        }
      }

      this.getEventType = function coalescenceBase_getEventType(aAction)
      {
        switch (aAction) {
          case kRemoveElm: case kHideElm:
            return EVENT_HIDE;
          case kAddElm: case kShowElm:
            return EVENT_SHOW;
        }
      }

      this.getActionName = function coalescenceBase_getActionName(aAction)
      {
        switch (aAction) {
          case kRemoveElm:
            return "remove";
          case kHideElm:
            return "hide";
          case kAddElm:
            return "add";
          case kShowElm:
            return "show";
          default:
            return "??";
        }
      }

      this.initSequence = function coalescenceBase_initSequence()
      {
        // expected events
        var eventType = this.getEventType(aParentAction);
        this.eventSeq = [
          new invokerChecker(eventType, this.parentNode),
          new invokerChecker(EVENT_REORDER, this.hostNode)
        ];

        // unexpected events
        this.unexpectedEventSeq = [
          new invokerChecker(this.getEventType(aChildAction), this.childNode),
          new invokerChecker(EVENT_REORDER, this.parentNode)
        ];
      }
    }

    /**
     * Remove or hide mutation events coalescence testing.
     */
    function removeOrHideCoalescenceBase(aChildID, aParentID,
                                         aChildAction, aParentAction,
                                         aPerformActionOnChildInTheFirstPlace)
    {
      this.__proto__ = new coalescenceBase(aChildAction, aParentAction,
                                           aPerformActionOnChildInTheFirstPlace);

      this.init = function removeOrHideCoalescenceBase_init()
      {
        this.childNode = getNode(aChildID);
        this.parentNode = getNode(aParentID);
        this.child = getAccessible(this.childNode);
        this.parent = getAccessible(this.parentNode);
        this.hostNode = this.parentNode.parentNode;
      }

      // Initalization

      this.init();
      this.initSequence();
    }

    ////////////////////////////////////////////////////////////////////////////
    // Invokers

    /**
     * Remove child node and then its parent node from DOM tree.
     */
    function removeChildNParent(aChildID, aParentID)
    {
      this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
                                                       kRemoveElm, kRemoveElm,
                                                       true);
    }

    /**
     * Remove parent node and then its child node from DOM tree.
     */
    function removeParentNChild(aChildID, aParentID)
    {
      this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
                                                       kRemoveElm, kRemoveElm,
                                                       false);
    }

    /**
     * Hide child node and then its parent node.
     */
    function hideChildNParent(aChildID, aParentID)
    {
      this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
                                                       kHideElm, kHideElm,
                                                       true);
    }

    /**
     * Hide parent node and then its child node.
     */
    function hideParentNChild(aChildID, aParentID)
    {
      this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
                                                       kHideElm, kHideElm,
                                                       false);
    }

    /**
     * Hide child node and then remove its parent node.
     */
    function hideChildNRemoveParent(aChildID, aParentID)
    {
      this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
                                                       kHideElm, kRemoveElm,
                                                       true);
    }

    /**
     * Hide parent node and then remove its child node.
     */
    function hideParentNRemoveChild(aChildID, aParentID)
    {
      this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
                                                       kRemoveElm, kHideElm,
                                                       false);
    }

    /**
     * Remove child node and then hide its parent node.
     */
    function removeChildNHideParent(aChildID, aParentID)
    {
      this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
                                                       kRemoveElm, kHideElm,
                                                       true);
    }

    /**
     * Remove parent node and then hide its child node.
     */
    function removeParentNHideChild(aChildID, aParentID)
    {
      this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
                                                       kHideElm, kRemoveElm,
                                                       false);
    }

    /**
     * Create and append parent node and create and append child node to it.
     */
    function addParentNChild(aHostID, aPerformActionOnChildInTheFirstPlace)
    {
      this.init = function addParentNChild_init()
      {
        this.hostNode = getNode(aHostID);
        this.parentNode = document.createElement("select");
        this.childNode = document.createElement("option");
        this.childNode.textContent = "testing";
      }

      this.__proto__ = new coalescenceBase(kAddElm, kAddElm,
                                           aPerformActionOnChildInTheFirstPlace);

      this.init();
      this.initSequence();
    }

    /**
     * Show parent node and show child node to it.
     */
    function showParentNChild(aParentID, aChildID,
                              aPerformActionOnChildInTheFirstPlace)
    {
      this.init = function showParentNChild_init()
      {
        this.parentNode = getNode(aParentID);
        this.hostNode = this.parentNode.parentNode;
        this.childNode = getNode(aChildID);
      }

      this.__proto__ = new coalescenceBase(kShowElm, kShowElm,
                                           aPerformActionOnChildInTheFirstPlace);

      this.init();
      this.initSequence();
    }

    /**
     * Create and append child node to the DOM and then show parent node.
     */
    function showParentNAddChild(aParentID,
                                 aPerformActionOnChildInTheFirstPlace)
    {
      this.init = function showParentNAddChild_init()
      {
        this.parentNode = getNode(aParentID);
        this.hostNode = this.parentNode.parentNode;
        this.childNode = document.createElement("option");
        this.childNode.textContent = "testing";
      }

      this.__proto__ = new coalescenceBase(kAddElm, kShowElm,
                                           aPerformActionOnChildInTheFirstPlace);

      this.init();
      this.initSequence();
    }

    /**
     * Remove children and parent
     */
    function removeGrandChildrenNHideParent(aChild1Id, aChild2Id, aParentId)
    {
      this.child1 = getNode(aChild1Id);
      this.child2 = getNode(aChild2Id);
      this.parent = getNode(aParentId);

      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getAccessible(aParentId)),
        new invokerChecker(EVENT_REORDER, getNode(aParentId).parentNode),
        new unexpectedInvokerChecker(EVENT_HIDE, getAccessible(aChild1Id)),
        new unexpectedInvokerChecker(EVENT_HIDE, getAccessible(aChild2Id)),
        new unexpectedInvokerChecker(EVENT_REORDER, getAccessible(aParentId))
      ];

      this.invoke = function removeGrandChildrenNHideParent_invoke()
      {
        this.child1.parentNode.removeChild(this.child1);
        this.child2.parentNode.removeChild(this.child2);
        this.parent.hidden = true;
      }

      this.getID = function removeGrandChildrenNHideParent_getID() {
        return "remove grand children of different parents and then hide their grand parent";
      }
    }

    /**
     * Remove a child, and then its parent.
     */
    function test3()
    {
      this.o = getAccessible("t3_o");
      this.ofc = getAccessible("t3_o").firstChild;

      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, this.o),
        new invokerChecker(EVENT_REORDER, "t3_lb"),
        new unexpectedInvokerChecker(EVENT_HIDE, this.ofc),
        new unexpectedInvokerChecker(EVENT_REORDER, this.o)
      ];

      this.invoke = function test3_invoke()
      {
        getNode("t3_o").textContent = "";
        getNode("t3_lb").removeChild(getNode("t3_o"));
      }

      this.finalCheck = function test3_finalCheck()
      {
        testIsDefunct(this.o);
        testIsDefunct(this.ofc);
      }

      this.getID = function test3_getID() {
        return "remove a child, and then its parent";
      }
    }

    /**
     * Remove children, and then a parent of 2nd child.
     */
    function test4()
    {
      this.o1 = getAccessible("t4_o1");
      this.o1fc = this.o1.firstChild;
      this.o2 = getAccessible("t4_o2");
      this.o2fc = this.o2.firstChild;

      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, this.o1fc),
        new invokerChecker(EVENT_HIDE, this.o2),
        new invokerChecker(EVENT_REORDER, "t4_lb"),
        new unexpectedInvokerChecker(EVENT_HIDE, this.o2fc),
        new unexpectedInvokerChecker(EVENT_REORDER, this.o1),
        new unexpectedInvokerChecker(EVENT_REORDER, this.o2)
      ];

      this.invoke = function test4_invoke()
      {
        getNode("t4_o1").textContent = "";
        getNode("t4_o2").textContent = "";
        getNode("t4_lb").removeChild(getNode("t4_o2"));
      }

      this.finalCheck = function test4_finalCheck()
      {
        testIsDefunct(this.o1fc);
        testIsDefunct(this.o2);
        testIsDefunct(this.o2fc);
      }

      this.getID = function test4_getID() {
        return "remove children, and then a parent of 2nd child";
      }
    }

    /**
     * Remove a child, remove a parent sibling, remove the parent
     */
    function test5()
    {
      this.o = getAccessible("t5_o");
      this.ofc = this.o.firstChild;
      this.b = getAccessible("t5_b");
      this.lb = getAccessible("t5_lb");

      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, this.b),
        new invokerChecker(EVENT_HIDE, this.o),
        new invokerChecker(EVENT_REORDER, "t5"),
        new unexpectedInvokerChecker(EVENT_HIDE, this.ofc),
        new unexpectedInvokerChecker(EVENT_REORDER, this.o),
        new unexpectedInvokerChecker(EVENT_REORDER, this.lb)
      ];

      this.invoke = function test5_invoke()
      {
        getNode("t5_o").textContent = "";
        getNode("t5").removeChild(getNode("t5_b"));
        getNode("t5_lb").removeChild(getNode("t5_o"));
      }

      this.finalCheck = function test5_finalCheck()
      {
        testIsDefunct(this.ofc);
        testIsDefunct(this.o);
        testIsDefunct(this.b);
      }

      this.getID = function test5_getID() {
        return "remove a child, remove a parent sibling, remove the parent";
      }
    }

    /**
     * Insert accessibles with a child node moved by aria-owns
     * Markup:
     * <div id="t6_fc">
     *   <div id="t6_owns"></div>
     * </div>
     * <div id="t6_sc" aria-owns="t6_owns"></div>
     */
    function test6()
    {
      this.parent = getNode("t6");
      this.fc = document.createElement("div");
      this.fc.setAttribute("id", "t6_fc");
      this.owns = document.createElement("div");
      this.owns.setAttribute("id", "t6_owns");
      this.sc = document.createElement("div");
      this.sc.setAttribute("id", "t6_sc");

      this.eventSeq = [
        new invokerChecker(EVENT_SHOW, this.fc),
        new invokerChecker(EVENT_SHOW, this.sc),
        new invokerChecker(EVENT_REORDER, this.parent),
        new unexpectedInvokerChecker(EVENT_REORDER, this.fc),
        new unexpectedInvokerChecker(EVENT_REORDER, this.sc),
        new unexpectedInvokerChecker(EVENT_HIDE, this.owns),
        new unexpectedInvokerChecker(EVENT_SHOW, this.owns)
      ];

      this.invoke = function test6_invoke()
      {
        getNode("t6").appendChild(this.fc);
        getNode("t6_fc").appendChild(this.owns);
        getNode("t6").appendChild(this.sc);
        getNode("t6_sc").setAttribute("aria-owns", "t6_owns");
      };

      this.getID = function test6_getID() {
        return "Insert accessibles with a child node moved by aria-owns";
      };
    }

    /**
     * Insert text nodes under direct and grand children, and then hide
     * their container by means of aria-owns.
     *
     * Markup:
     * <div id="t7_moveplace" aria-owns="t7_c"></div>
     * <div id="t7_c">
     *   <div id="t7_c_directchild">ha</div>
     *   <div><div id="t7_c_grandchild">ha</div></div>
     * </div>
     */
    function test7()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getNode('t7_c')),
        new invokerChecker(EVENT_SHOW, getNode('t7_c')),
        new invokerChecker(EVENT_REORDER, getNode('t7')),
        new unexpectedInvokerChecker(EVENT_REORDER, getNode('t7_c_directchild')),
        new unexpectedInvokerChecker(EVENT_REORDER, getNode('t7_c_grandchild')),
        new unexpectedInvokerChecker(EVENT_SHOW, () => getNode('t7_c_directchild').firstChild),
        new unexpectedInvokerChecker(EVENT_SHOW, () => getNode('t7_c_grandchild').firstChild)
      ];

      this.invoke = function test7_invoke()
      {
        getNode('t7_c_directchild').textContent = 'ha';
        getNode('t7_c_grandchild').textContent = 'ha';
        getNode('t7_moveplace').setAttribute('aria-owns', 't7_c');
      };

      this.getID = function test7_getID() {
        return "Show child accessibles and then hide their container";
      };
    }

    /**
     * Move a node by aria-owns from right to left in the tree, so that
     * the eventing looks this way:
     * reorder for 't8_c1'
     *   hide for 't8_c1_child'
     *   show for 't8_c2_moved'
     * reorder for 't8_c2'
     *   hide for 't8_c2_moved'
     *
     * The hide event should be delivered before the paired show event.
     */
    function test8()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getNode('t8_c1_child')),
        new invokerChecker(EVENT_HIDE, 't8_c2_moved'),
        new invokerChecker(EVENT_SHOW, 't8_c2_moved'),
        new invokerChecker(EVENT_REORDER, 't8_c2'),
        new invokerChecker(EVENT_REORDER, 't8_c1'),
      ];

      this.invoke = function test8_invoke()
      {
        // Remove a node from 't8_c1' container to give the event tree a
        // desired structure (the 't8_c1' container node goes first in the event
        // tree)
        getNode('t8_c1_child').remove();
        // then move 't8_c2_moved' from 't8_c2' to 't8_c1'.
        getNode('t8_c1').setAttribute('aria-owns', 't8_c2_moved');
      };

      this.getID = function test8_getID() {
        return "Move a node by aria-owns to left within the tree";
      };
    }

    /**
     * Move 't9_c3_moved' node under 't9_c2_moved', and then move 't9_c2_moved'
     * node by aria-owns (same as test10 but has different aria-owns
     * ordering), the eventing looks same way as in test10:
     * reorder for 't9_c1'
     *   hide for 't9_c1_child'
     *   show for 't9_c2_moved'
     * reorder for 't9_c2'
    *    hide for 't9_c2_child'
     *   hide for 't9_c2_moved'
     * reorder for 't9_c3'
     *   hide for 't9_c3_moved'
     *
     * The hide events for 't9_c2_moved' and 't9_c3_moved' should be delivered
     * before the show event for 't9_c2_moved'.
     */
    function test9()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getNode('t9_c1_child')),
        new invokerChecker(EVENT_HIDE, getNode('t9_c2_child')),
        new invokerChecker(EVENT_HIDE, 't9_c3_moved'),
        new invokerChecker(EVENT_HIDE, 't9_c2_moved'),
        new invokerChecker(EVENT_SHOW, 't9_c2_moved'),
        new invokerChecker(EVENT_REORDER, 't9_c3'),
        new invokerChecker(EVENT_REORDER, 't9_c2'),
        new invokerChecker(EVENT_REORDER, 't9_c1'),
        new unexpectedInvokerChecker(EVENT_SHOW, 't9_c3_moved')
      ];

      this.invoke = function test9_invoke()
      {
        // Remove child nodes from 't9_c1' and 't9_c2' containers to give
        // the event tree a needed structure ('t9_c1' and 't9_c2' nodes go
        // first in the event tree),
        getNode('t9_c1_child').remove();
        getNode('t9_c2_child').remove();
        // then do aria-owns magic.
        getNode('t9_c2_moved').setAttribute('aria-owns', 't9_c3_moved');
        getNode('t9_c1').setAttribute('aria-owns', 't9_c2_moved');
      };

      this.getID = function test9_getID() {
        return "Move node #1 by aria-owns and then move node #2 into node #1";
      };
    }

    /**
     * Move a node 't10_c3_moved' by aria-owns under a node 't10_c2_moved',
     * moved by under 't10_1', so that the eventing looks this way:
     * reorder for 't10_c1'
     *   hide for 't10_c1_child'
     *   show for 't10_c2_moved'
     * reorder for 't10_c2'
     *   hide for 't10_c2_child'
     *   hide for 't10_c2_moved'
     * reorder for 't10_c3'
     *   hide for 't10_c3_moved'
     *
     * The hide events for 't10_c2_moved' and 't10_c3_moved' should be delivered
     * before the show event for 't10_c2_moved'.
     */
    function test10()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getNode('t10_c1_child')),
        new invokerChecker(EVENT_HIDE, getNode('t10_c2_child')),
        new invokerChecker(EVENT_HIDE, getNode('t10_c2_moved')),
        new invokerChecker(EVENT_HIDE, getNode('t10_c3_moved')),
        new invokerChecker(EVENT_SHOW, getNode('t10_c2_moved')),
        new invokerChecker(EVENT_REORDER, 't10_c2'),
        new invokerChecker(EVENT_REORDER, 't10_c1'),
        new invokerChecker(EVENT_REORDER, 't10_c3')
      ];

      this.invoke = function test10_invoke()
      {
        // Remove child nodes from 't10_c1' and 't10_c2' containers to give
        // the event tree a needed structure ('t10_c1' and 't10_c2' nodes go first
        // in the event tree),
        getNode('t10_c1_child').remove();
        getNode('t10_c2_child').remove();
        // then do aria-owns stuff.
        getNode('t10_c1').setAttribute('aria-owns', 't10_c2_moved');
        getNode('t10_c2_moved').setAttribute('aria-owns', 't10_c3_moved');
      };

      this.getID = function test10_getID() {
        return "Move a node by aria-owns into a node moved by aria-owns to left within the tree";
      };
    }

    /**
     * Move a node by aria-owns from right to left in the tree, and then
     * move its parent too by aria-owns. No hide event should be fired for
     * original node.
     */
    function test11()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getNode('t11_c1_child')),
        new invokerChecker(EVENT_HIDE, getNode('t11_c2')),
        new orderChecker(),
        new asyncInvokerChecker(EVENT_SHOW, 't11_c2_child'),
        new asyncInvokerChecker(EVENT_SHOW, 't11_c2'),
        new orderChecker(),
        new invokerChecker(EVENT_REORDER, 't11'),
        new unexpectedInvokerChecker(EVENT_HIDE, 't11_c2_child'),
        new unexpectedInvokerChecker(EVENT_REORDER, 't11_c1'),
        new unexpectedInvokerChecker(EVENT_REORDER, 't11_c2'),
        new unexpectedInvokerChecker(EVENT_REORDER, 't11_c3')
      ];

      this.invoke = function test11_invoke()
      {
        // Remove a node from 't11_c1' container to give the event tree a
        // desired structure (the 't11_c1' container node goes first in
        // the event tree),
        getNode('t11_c1_child').remove();
        // then move 't11_c2_moved' from 't11_c2' to 't11_c1', and then move
        // 't11_c2' to 't11_c3'.
        getNode('t11_c1').setAttribute('aria-owns', 't11_c2_child');
        getNode('t11_c3').setAttribute('aria-owns', 't11_c2');
      };

      this.getID = function test11_getID() {
        return "Move a node by aria-owns to left within the tree";
      };
    }

    ////////////////////////////////////////////////////////////////////////////
    // Do tests.

    gA11yEventDumpToConsole = true; // debug stuff
    //enableLogging("eventTree");

    var gQueue = null;
    function doTests()
    {
      gQueue = new eventQueue();

      gQueue.push(new removeChildNParent("option1", "select1"));
      gQueue.push(new removeParentNChild("option2", "select2"));
      gQueue.push(new hideChildNParent("option3", "select3"));
      gQueue.push(new hideParentNChild("option4", "select4"));
      gQueue.push(new hideChildNRemoveParent("option5", "select5"));
      gQueue.push(new hideParentNRemoveChild("option6", "select6"));
      gQueue.push(new removeChildNHideParent("option7", "select7"));
      gQueue.push(new removeParentNHideChild("option8", "select8"));

      gQueue.push(new addParentNChild("testContainer", false));
      gQueue.push(new addParentNChild("testContainer", true));
      gQueue.push(new showParentNChild("select9", "option9", false));
      gQueue.push(new showParentNChild("select10", "option10", true));
      gQueue.push(new showParentNAddChild("select11", false));
      gQueue.push(new showParentNAddChild("select12", true));

      gQueue.push(new removeGrandChildrenNHideParent("t1_child1", "t1_child2", "t1_parent"));
      gQueue.push(new test3());
      gQueue.push(new test4());
      gQueue.push(new test5());
      gQueue.push(new test6());
      gQueue.push(new test7());
      gQueue.push(new test8());
      gQueue.push(new test9());
      gQueue.push(new test10());
      gQueue.push(new test11());

      gQueue.invoke(); // Will call SimpleTest.finish();
    }

    SimpleTest.waitForExplicitFinish();
    addA11yLoadEvent(doTests);
  </script>
</head>

<body>

  <a target="_blank"
     href="https://bugzilla.mozilla.org/show_bug.cgi?id=513213"
     title="coalesce events when new event is appended to the queue">
    Mozilla Bug 513213
  </a><br>
  <a target="_blank"
     title="Rework accessible tree update code"
     href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
    Mozilla Bug 570275
  </a>

  <p id="display"></p>
  <div id="content" style="display: none"></div>
  <pre id="test">
  </pre>

  <div id="testContainer">
    <select id="select1">
      <option id="option1">option</option>
    </select>
    <select id="select2">
      <option id="option2">option</option>
    </select>
    <select id="select3">
      <option id="option3">option</option>
    </select>
    <select id="select4">
      <option id="option4">option</option>
    </select>
    <select id="select5">
      <option id="option5">option</option>
    </select>
    <select id="select6">
      <option id="option6">option</option>
    </select>
    <select id="select7">
      <option id="option7">option</option>
    </select>
    <select id="select8">
      <option id="option8">option</option>
    </select>

    <select id="select9" style="display: none">
      <option id="option9" style="display: none">testing</option>
    </select>
    <select id="select10" style="display: none">
      <option id="option10" style="display: none">testing</option>
    </select>
    <select id="select11" style="display: none"></select>
    <select id="select12" style="display: none"></select>
  </div>

  <div id="testContainer2">
    <div id="t1_parent">
      <div id="t1_mid1"><div id="t1_child1"></div></div>
      <div id="t1_mid2"><div id="t1_child2"></div></div>
    </div>
  </div>

  <div id="t3">
    <div role="listbox" id="t3_lb">
      <div role="option" id="t3_o">opt</div>
    </div>
  </div>

  <div id="t4">
    <div role="listbox" id="t4_lb">
      <div role="option" id="t4_o1">opt1</div>
      <div role="option" id="t4_o2">opt2</div>
    </div>
  </div>

  <div id="t5">
    <div role="button" id="t5_b">btn</div>
    <div role="listbox" id="t5_lb">
      <div role="option" id="t5_o">opt</div>
    </div>
  </div>

  <div id="t6">
  </div>

  <div id="t7">
    <div id="t7_moveplace"></div>
    <div id="t7_c">
      <div><div id="t7_c_grandchild"></div></div>
      <div id="t7_c_directchild"></div>
    </div>
  </div>

  <div id="t8">
    <div id="t8_c1"><div id="t8_c1_child"></div></div>
    <div id="t8_c2">
      <div id="t8_c2_moved"></div>
    </div>
  </div>

  <div id="t9">
    <div id="t9_c1"><div id="t9_c1_child"></div></div>
    <div id="t9_c2">
      <div id="t9_c2_child"></div>
      <div id="t9_c2_moved"></div>
    </div>
    <div id="t9_c3">
      <div id="t9_c3_moved"></div>
    </div>
  </div>

  <div id="t10">
    <div id="t10_c1"><div id="t10_c1_child"></div></div>
    <div id="t10_c2">
      <div id="t10_c2_child"></div>
      <div id="t10_c2_moved"></div>
    </div>
    <div id="t10_c3">
      <div id="t10_c3_moved"></div>
    </div>
  </div>

  <div id="t11">
    <div id="t11_c1"><div id="t11_c1_child"></div></div>
    <div id="t11_c2"><div id="t11_c2_child"></div></div>
    <div id="t11_c3"></div>
  </div>
</body>
</html>
